// Copyright (c) 2009 Mike Haight, Andy Kipp
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

// Microsoft Visual C++ complier does not support export templates.
// This means that the template classes must be defined in the header.

#pragma once

#include "stdafx.h"
#include "Object.h"
#include "Location.h"
#include "Size.h"

namespace HandBanana { namespace Core
{
    /// <summary>
    /// Provides a base class for event arguments.
    /// </summary>
    class EventArgs : public Object
    {
    // Constructors
    public:
        EventArgs(void)
        {
        }


    // Destructor
    public:
        virtual ~EventArgs(void)
        {
        }

    };


    /// <summary>
    /// Provides a class for move events.
    /// </summary>
    class MoveEventArgs : public EventArgs
    {
    // Constructors
    public:
        MoveEventArgs(void) : mX(0), mY(0)
        {
        }

        MoveEventArgs(Location& argLocation) : mX(argLocation.X), mY(argLocation.Y)
        {
        }

        MoveEventArgs(float argX, float argY) : mX(argX), mY(argY)
        {
        }


    // Destructor
    public:
        virtual ~MoveEventArgs(void)
        {
        }


    // Properties
    public:
        /// <summary>
        /// Returns the x value of the event.
        /// </summary>
        float X(void) const
        {
            return mX;
        }

        /// <summary>
        /// Returns the y value of the event.
        /// </summary>
        float Y(void) const
        {
            return mY;
        }


    // Private Data Members
    private:
        float mX, mY;

    };


    /// <summary>
    /// Provides a class for size events.
    /// </summary>
    class SizeEventArgs : public EventArgs
    {
    // Constructors
    public:
        SizeEventArgs(void) : mWidth(0), mHeight(0)
        {
        }

        SizeEventArgs(Size& argSize) : mWidth(argSize.Width), mHeight(argSize.Height)
        {
        }

        SizeEventArgs(float argWidth, float argHeight) : mWidth(argWidth), mHeight(argHeight)
        {
        }


    // Destructor
    public:
        virtual ~SizeEventArgs(void)
        {
        }


    // Properties
    public:
        /// <summary>
        /// Returns the width value of the event.
        /// </summary>
        float Width(void) const
        {
            return mWidth;
        }

        /// <summary>
        /// Returns the height value of the event.
        /// </summary>
        float Height(void) const
        {
            return mHeight;
        }


    // Private Data Members
    private:
        float mWidth, mHeight;

    };


    /// <summary>
    /// The base class for eventing delegates.
    /// </summary>
    template <typename EventType>
    class Delegate : public Object
    {
    // Public Methods
    public:
        /// <summary>
        /// Returns true if this instance of Delegate is equal to the provided instance.
        /// </summary>
        virtual bool Equals(const Delegate<EventType>& argOther) const = NULL;

        /// <summary>
        /// Calls the delegate function.
        /// </summary>
        virtual void Invoke(Object& argSender, EventType& argParameters) = NULL;


    // Object Overrides
    public:
        /// <summary>
        /// Overrides Object::Equals().
        /// </summary>
        virtual bool Equals(const Object& argOther) const
        {
            try
            {
                return this->Equals(dynamic_cast<const Delegate<EventType>&>(argOther));
            }
            catch (std::bad_cast&)
            {
                return false;
            }
        }

    };


    /// <summary>
    /// Provides a delegate for functions without class instances.
    /// </summary>
    template <typename EventType>
    class FunctionDelegate : public Delegate<EventType>
    {
    // Constructor
    public:
        /// <summary>
        /// Creates a new delegate that points to a function.
        /// </summary>
        FunctionDelegate(void (*argCallback)(Object& argSender, EventType& argParameters))
        {
            THROW_IF_NULL(argCallback, "Callback cannot be NULL");

            mCallback = argCallback;
        }


    // Delegate Methods
    public:
        /// <summary>
        /// Calls the delegate function.
        /// </summary>
        virtual void Invoke(Object& argSender, EventType& argParameters)
        {
            mCallback(argSender, argParameters);
        }

        /// <summary>
        /// Returns true if this instance of FunctionDelegate is equal to the provided instance of Delegate.
        /// </summary>
        virtual bool Equals(const Delegate<EventType>& argOther) const
        {
            try
            {
                const FunctionDelegate<EventType>& d = dynamic_cast<const FunctionDelegate<EventType>&>(argOther);
                return (mCallback == d.mCallback);
            }
            catch (std::bad_cast&)
            {
                return false;
            }
        }


    // Private Data Members
    private:
        void (*mCallback)(Object& argSender, EventType& argParameters);

    };


    /// <summary>
    /// Provides a delegate for functions with class instances.
    /// </summary>
    template <typename TargetType, typename EventType>
    class MemberDelegate : public Delegate<EventType>
    {
    // Constructor
    public:
        /// <summary>
        /// Creates a new delegate that points to a function in an instance of a class.
        /// </summary>
        MemberDelegate(TargetType* argTarget, void (TargetType::*argCallback)(Object& argSender, EventType& argParameters))
        {
            THROW_IF_NULL(argTarget, "Target cannot be NULL");
            THROW_IF_NULL(argCallback, "Callback cannot be NULL");

            mTarget = argTarget;
            mCallback = argCallback;
        }


    // Delegate Methods
    public:
        /// <summary>
        /// Calls the delegate function.
        /// </summary>
        virtual void Invoke(Object& argSender, EventType& argParameters)
        {
            (mTarget->*mCallback)(argSender, argParameters);
        }

        /// <summary>
        /// Returns true if this instance of MemberDelegate is equal to the provided instance of Delegate.
        /// </summary>
        virtual bool Equals(const Delegate<EventType>& argOther) const
        {
            try
            {
                const MemberDelegate<TargetType,EventType>& d = dynamic_cast<const MemberDelegate<TargetType,EventType>&>(argOther);
                return (mTarget == d.mTarget) && (mCallback == d.mCallback);
            }
            catch (std::bad_cast&)
            {
                return false;
            }
        }


    // Private Data Members
    private:
        TargetType* mTarget;
        void (TargetType::*mCallback)(Object& argSender, EventType& argParameters);

    };


    /// <summary>
    /// Provides eventing.
    /// </summary>
    template <typename EventType>
    class Event
    {
    // Constructor
    public:
        /// <summary>
        /// Creates a new instance of Event.
        /// </summary>
        Event(void)
        {
        }


    // Destructor
    public:
        /// <summary>
        /// Cleans up all of the delegates that have been registered with this Event.
        /// </summary>
        ~Event(void)
        {
            mDelegates.clear();
        }


    // Properties
    public:
        /// <summary>
        /// Returns the number of delegates that have been registered with this event.
        /// </summary>
        int ListenerCount(void) const
        {
            mDelegates.size();
        }


    // Public Methods
    public:
        /// <summary>
        /// Clears this event of all of the delegate that have been registered with this event.
        /// </summary>
        void Clear(void)
        {
            mDelegates.clear();
        }

        /// <summary>
        /// Raises the event for all listeners.
        /// </summary>
        void Raise(Object& argSender, EventType& argParameters)
        {
            for (std::vector<boost::shared_ptr<Delegate<EventType>>>::iterator current = mDelegates.begin(); current != mDelegates.end(); ++current)
            {
                (*current)->Invoke(argSender, argParameters);
            }
        }


    // Operator Overloads
    public:
        /// <summary>
        /// Provides a means by which a listener can add a delegate to this event.
        /// </summary>
        void operator+=(const boost::shared_ptr<Delegate<EventType>> argRhs)
        {
            for (std::vector<boost::shared_ptr<Delegate<EventType>>>::iterator current = mDelegates.begin(); current != mDelegates.end(); ++current)
            {
                // If the event already contains an equal delegate, just return
                if (argRhs->Equals((Delegate<EventType>&)**current))
                {
                    return;
                }
            }

            mDelegates.push_back(argRhs);
        }

        /// <summary>
        /// Provides a means by which a listener can remove a delegate from this event.
        /// </summary>
        void operator-=(const boost::shared_ptr<Delegate<EventType>> argRhs)
        {
            for (std::vector<boost::shared_ptr<Delegate<EventType>>>::iterator current = mDelegates.begin(); current != mDelegates.end(); ++current)
            {
                // If the event contains an equal delegate, erase it
                if (argRhs->Equals(*current))
                {
                    mDelegates.erase(current);
                    break;
                }
            }
        }


    // Private Data Members
    private:
        std::vector<boost::shared_ptr<Delegate<EventType>>> mDelegates;

    };
} }
